<!doctype html>



  


<html class="theme-next muse use-motion">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>



<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />












  
  
  <link href="/vendors/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />




  
  
  
  

  
    
    
  

  

  

  

  

  
    
    
    <link href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
  






<link href="/vendors/font-awesome/css/font-awesome.min.css?v=4.4.0" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.0.1" rel="stylesheet" type="text/css" />


  <meta name="keywords" content="spring,ioc," />








  <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=5.0.1" />






<meta name="description" content="外部容器概述
原理分析
小结



上一篇讲了内部容器的初始化过程，那么自然这篇就讲讲外部容器的初始化过程。">
<meta property="og:type" content="article">
<meta property="og:title" content="从源码解析SpringIoc机制（2）">
<meta property="og:url" content="http://yoursite.com/2016/08/28/spring-ioc-2/index.html">
<meta property="og:site_name" content="个人博客">
<meta property="og:description" content="外部容器概述
原理分析
小结



上一篇讲了内部容器的初始化过程，那么自然这篇就讲讲外部容器的初始化过程。">
<meta property="og:image" content="http://www.blogjava.net/images/blogjava_net/zhanglongsr/container_outer.jpg">
<meta property="og:updated_time" content="2017-01-19T07:14:13.932Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="从源码解析SpringIoc机制（2）">
<meta name="twitter:description" content="外部容器概述
原理分析
小结



上一篇讲了内部容器的初始化过程，那么自然这篇就讲讲外部容器的初始化过程。">
<meta name="twitter:image" content="http://www.blogjava.net/images/blogjava_net/zhanglongsr/container_outer.jpg">



<script type="text/javascript" id="hexo.configuration">
  var NexT = window.NexT || {};
  var CONFIG = {
    scheme: 'Muse',
    sidebar: {"position":"left","display":"post"},
    fancybox: true,
    motion: true,
    duoshuo: {
      userId: 0,
      author: '博主'
    }
  };
</script>




  <link rel="canonical" href="http://yoursite.com/2016/08/28/spring-ioc-2/"/>

  <title> 从源码解析SpringIoc机制（2） | 个人博客 </title>
</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  










  
  
    
  

  <div class="container one-collumn sidebar-position-left page-post-detail ">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-meta ">
  

  <div class="custom-logo-site-title">
    <a href="/"  class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <span class="site-title">个人博客</span>
      <span class="logo-line-after"><i></i></span>
    </a>
  </div>
  <p class="site-subtitle">随便写点什么</p>
</div>

<div class="site-nav-toggle">
  <button>
    <span class="btn-bar"></span>
    <span class="btn-bar"></span>
    <span class="btn-bar"></span>
  </button>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />
            
            关于
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      

      
    </ul>
  

  
</nav>

 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  
  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                从源码解析SpringIoc机制（2）
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            <span class="post-meta-item-icon">
              <i class="fa fa-calendar-o"></i>
            </span>
            <span class="post-meta-item-text">发表于</span>
            <time itemprop="dateCreated" datetime="2016-08-28T15:43:53+08:00" content="2016-08-28">
              2016-08-28
            </time>
          </span>

          
            <span class="post-category" >
              &nbsp; | &nbsp;
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
              
                <span itemprop="about" itemscope itemtype="https://schema.org/Thing">
                  <a href="/categories/spring/" itemprop="url" rel="index">
                    <span itemprop="name">spring</span>
                  </a>
                </span>

                
                

              
            </span>
          

          
            
              <span class="post-comments-count">
                &nbsp; | &nbsp;
                <a href="/2016/08/28/spring-ioc-2/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count ds-thread-count" data-thread-key="2016/08/28/spring-ioc-2/" itemprop="commentsCount"></span>
                </a>
              </span>
            
          

          

          
          

          
        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        <hr>
<blockquote>
<ul>
<li>外部容器概述</li>
<li>原理分析</li>
<li>小结</li>
</ul>
</blockquote>
<hr>
<p>上一篇讲了内部容器的初始化过程，那么自然这篇就讲讲外部容器的初始化过程。</p>
<a id="more"></a>
<h3 id="1-外部容器概述"><a href="#1-外部容器概述" class="headerlink" title="1.外部容器概述"></a>1.外部容器概述</h3><p>这里把ApplicationContext该分支成为外部容器，为啥呢，因为在程序中我们很少会直接获取一个BeanFactory来操作，通常都是通过ApplicationContext来做的，Spring把ApplicationContext称为高级IoC容器，确实没错，他不仅有BeanFactory的所有功能，它还具有一些内部容器不具备的功能，比如支持多信息源。</p>
<p>好，还是来看看外部容器的层级示意图<br><img src="http://www.blogjava.net/images/blogjava_net/zhanglongsr/container_outer.jpg" alt="out-container"><br>没错，这里ApplicationContext依旧是继承了ListableBeanFactory与HierarchicalBeanFactory，因为我们最基本的功能还是在这几个接口定义的。总体层级看下来，比较不同的就是它解析资源的方式，不过这也是因为他支持多信息源所导致的。可以看到AbstractRefreshableApplicationContext继承了DefaultListableBeanFactory，也就是说明，注册bean等大部分通用操作依旧走的是默认的DefaultListableBeanFactory。相当于是在DefaultListableBeanFactory的基础上扩展。</p>
<p>这里在简单介绍下图中接口或者抽象类的功能：</p>
<p>1.ApplicationContext： 首先是一个外部容器的中心接口，提供应用程序的配置。根据官方释意：一个ApplicationContext提供如下功能：</p>
<blockquote>
<p>·用来访问应用程序组件的bean factory方法，其继承自ListableBeanFactory。<br>    ·能以通用的方式加载文件资源，其继承自ResourceLoader。<br>        ·能够向注册的监听器发布事件，其继承自ApplicationEventPublisher。<br>        ·能够解析消息，支持国际化，继承自MessageSource。<br>        ·继承自父上下文，后代上下文中的Definition将总能获得优先级，这意味着，例如，一个单亲上下文能够被整个应用程序使用，而每个servlet有它自己的孩子上下文，它独立于其他的servlet。</p>
</blockquote>
<p>2.ConfigurableApplicationContext：配置和生命周期方法被封装在这里。</p>
<p>3.AbstractApplicationContext：ApplicationContext的基本抽象实现，提供了BeanFactory的后置processor的注册、应用上下文的双亲维护、资源加载的功能。</p>
<p>4.AbstractRefreshableApplicationContext：官方释义，ApplicationContext的基类的实现，其支持多次refreshs，每次创建一个内部bean工厂实例。通常（但不一定），一个上下文将会由一系列的配置定位来驱动，加载bean definations。</p>
<p>5.AbstractRefreshableConfigApplicationContext：主要为配置文件位置的设置提供入口，即实现了setConfigLocation方法。这就提供了不依赖于Spring的且更灵活、通用的配置注入方式。</p>
<p>6.AbstractXmlApplicationContext：ApplicationContext的便利基类的实现。用于提取包含bean definition信息的XML文档，该文档由XmlBeanDefinitionReader负责解释。</p>
<p>7.FileSystemXmlApplicationContext：这是一个简单的，一站式的便利的ApplicationContext的基本实现。</p>
<p>8.ResourceLoader：加载资源(类路径和文件路径)的策略接口，ApplicationContext需要提供此功能来加载资源。</p>
<p>9.ResourcePatternResolver：解释位置模式的策略接口，其扩展自ResourcePatternResolver接口。</p>
<h3 id="2-原理分析"><a href="#2-原理分析" class="headerlink" title="2.原理分析"></a>2.原理分析</h3><p>网上已经有很多资源具体解析了如何从FileSystemXmlApplicationContext启动一个IoC容器，那这里就换个差不多的容器ClassPathXmlApplicationContext来着手说明。</p>
<p>先看下ClassPathXmlApplicationContext的入口<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line">* configLocations：资源路径名数组</div><div class="line">* refresh：是否自动刷新context</div><div class="line">* parent：父context（因为ApplicationContext继承了HierarchicalBeanFactory），可为null</div><div class="line">*/</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">ClassPathXmlApplicationContext</span><span class="params">(String[] configLocations, <span class="keyword">boolean</span> refresh, ApplicationContext parent)</span></span></div><div class="line">			<span class="keyword">throws</span> BeansException &#123;</div><div class="line"></div><div class="line">		<span class="keyword">super</span>(parent);</div><div class="line">		<span class="comment">//设置资源，该方法在其父类中，就是把传入的资源路径数组设置到父类中</span></div><div class="line">		setConfigLocations(configLocations);</div><div class="line">		<span class="keyword">if</span> (refresh) &#123;</div><div class="line">		<span class="comment">//最为关键的一步，该方法在AbstractApplicationContext中已经实现</span></div><div class="line">			refresh();</div><div class="line">		&#125;</div><div class="line">	&#125;</div></pre></td></tr></table></figure></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">refresh</span><span class="params">()</span> <span class="keyword">throws</span> BeansException, IllegalStateException </span>&#123;  </div><div class="line">        <span class="keyword">synchronized</span> (<span class="keyword">this</span>.startupShutdownMonitor) &#123;  </div><div class="line">            <span class="comment">// 容器启动的预先准备，记录容器启动的时间和标记  </span></div><div class="line">            prepareRefresh();  </div><div class="line">  </div><div class="line">            <span class="comment">// 创建BeanFactory，如果已有就销毁，没有就创建。此类实现了对BeanDefinition的装载 </span></div><div class="line">            <span class="comment">//这是重点关注的部分，bean的加载注册也是在这里完成的</span></div><div class="line">            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();  </div><div class="line">  </div><div class="line">            <span class="comment">// 配置BeanFactory标准上下文特性，如类装载器，PostProcesser等  </span></div><div class="line">            prepareBeanFactory(beanFactory);  </div><div class="line">  </div><div class="line">            <span class="keyword">try</span> &#123;  </div><div class="line">                <span class="comment">// 在bean被装载后，提供一个修改BeanFactory的入口  </span></div><div class="line">                postProcessBeanFactory(beanFactory);  </div><div class="line">  </div><div class="line">                <span class="comment">// 调用postProcessBeanFactory  </span></div><div class="line">                invokeBeanFactoryPostProcessors(beanFactory);  </div><div class="line">  </div><div class="line">                <span class="comment">// 注册用于拦截bean创建过程中的BeanPostProcessors  </span></div><div class="line">                registerBeanPostProcessors(beanFactory);  </div><div class="line">  </div><div class="line">                <span class="comment">// Initialize message source for this context.  </span></div><div class="line">                initMessageSource();  </div><div class="line">  </div><div class="line">                <span class="comment">// Initialize event multicaster for this context.  </span></div><div class="line">                initApplicationEventMulticaster();  </div><div class="line">  </div><div class="line">                <span class="comment">// Initialize other special beans in specific context subclasses.  </span></div><div class="line">                onRefresh();  </div><div class="line">  </div><div class="line">                <span class="comment">// 注册监听器  </span></div><div class="line">                registerListeners();  </div><div class="line">  </div><div class="line">                <span class="comment">// 完成容器的初始化，里面的preInstantiateSingletons()完成对单例对象的创建  </span></div><div class="line">                finishBeanFactoryInitialization(beanFactory);  </div><div class="line">  </div><div class="line">                <span class="comment">// Last step: publish corresponding event.  </span></div><div class="line">                finishRefresh();  </div><div class="line">            &#125;  </div><div class="line">  </div><div class="line">            <span class="keyword">catch</span> (BeansException ex) &#123;  </div><div class="line">                <span class="comment">// Destroy already created singletons to avoid dangling resources.  </span></div><div class="line">                beanFactory.destroySingletons();  </div><div class="line">  </div><div class="line">                <span class="comment">// Reset 'active' flag.  </span></div><div class="line">                cancelRefresh(ex);  </div><div class="line">  </div><div class="line">                <span class="comment">// Propagate exception to caller.  </span></div><div class="line">                <span class="keyword">throw</span> ex;  </div><div class="line">            &#125;  </div><div class="line">        &#125;  </div><div class="line">    &#125;</div></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">protected</span> ConfigurableListableBeanFactory <span class="title">obtainFreshBeanFactory</span><span class="params">()</span> </span>&#123;</div><div class="line">		refreshBeanFactory();</div><div class="line">		ConfigurableListableBeanFactory beanFactory = getBeanFactory();</div><div class="line">		<span class="keyword">if</span> (logger.isDebugEnabled()) &#123;</div><div class="line">			logger.debug(<span class="string">"Bean factory for "</span> + getDisplayName() + <span class="string">": "</span> + beanFactory);</div><div class="line">		&#125;</div><div class="line">		<span class="keyword">return</span> beanFactory;</div><div class="line">	&#125;</div><div class="line">	<span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">refreshBeanFactory</span><span class="params">()</span> <span class="keyword">throws</span> BeansException </span>&#123;</div><div class="line">	<span class="comment">//如果此时内部的beanfactory已经创建好了，那么销毁已经注册的bean，同时关闭这个factory</span></div><div class="line">		<span class="keyword">if</span> (hasBeanFactory()) &#123;</div><div class="line">			destroyBeans();<span class="comment">//有DefaultListableBeanFactory发起，clear它内部用于维护bean的map有list，同时可clear其父类中用于维护bean与bean之间关系的map，list</span></div><div class="line">			closeBeanFactory(); <span class="comment">//将内部的beanfactory置空</span></div><div class="line">		&#125;</div><div class="line">	<span class="comment">//如果此时内部的beanfactory还没有创建，那么创建一个beanfactory，默认都是DefaultListableBeanFactory</span></div><div class="line">		<span class="keyword">try</span> &#123;</div><div class="line">			DefaultListableBeanFactory beanFactory = createBeanFactory();</div><div class="line">			beanFactory.setSerializationId(getId());</div><div class="line">			<span class="comment">//设置这个beanfactory的一些属性，我看了下设置了两个部分  1.是否允许BeanDefinition的覆盖  2.是否允许循环引用</span></div><div class="line">			customizeBeanFactory(beanFactory);</div><div class="line">			<span class="comment">//即将开始读取resource，往这个beanfactory里注册BeanDefinition</span></div><div class="line">			<span class="comment">//这是加载注册bean的入口</span></div><div class="line">			loadBeanDefinitions(beanFactory);</div><div class="line">			<span class="comment">//将现在创建好的beanfactory设置为当前持有的beanfactory</span></div><div class="line">			<span class="keyword">synchronized</span> (<span class="keyword">this</span>.beanFactoryMonitor) &#123;</div><div class="line">				<span class="keyword">this</span>.beanFactory = beanFactory;</div><div class="line">			&#125;</div><div class="line">		&#125;</div><div class="line">		<span class="keyword">catch</span> (IOException ex) &#123;</div><div class="line">			<span class="keyword">throw</span> <span class="keyword">new</span> ApplicationContextException(<span class="string">"I/O error parsing bean definition source for "</span> + getDisplayName(), ex);</div><div class="line">		&#125;</div><div class="line">	&#125;</div></pre></td></tr></table></figure>
<p>接下来看下，是如何获得Resource，进行bean的加载的</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">loadBeanDefinitions</span><span class="params">(DefaultListableBeanFactory beanFactory)</span> <span class="keyword">throws</span> BeansException, IOException </span>&#123;</div><div class="line">		<span class="comment">//跟内部容易一样，要加载资源，那就要定义一个读取资源的读取器，同之前XmlBeanFactory一样也是创建一个XmlBeanDefinitionReader</span></div><div class="line">		XmlBeanDefinitionReader beanDefinitionReader = <span class="keyword">new</span> XmlBeanDefinitionReader(beanFactory);</div><div class="line"></div><div class="line">		</div><div class="line">		beanDefinitionReader.setEnvironment(<span class="keyword">this</span>.getEnvironment());</div><div class="line">		<span class="comment">//从容器的层级模型图中看出，AbstractApplicationContext实现了ResourceLoader，提供了一个默认的DefaultResourceLoader用于加载以不同形式给出的资源路径</span></div><div class="line">		<span class="comment">//给Reader配置一个ResourceLoader，以读取DefaultResourceLoader加载出来的Resource</span></div><div class="line">		beanDefinitionReader.setResourceLoader(<span class="keyword">this</span>);</div><div class="line"></div><div class="line">		<span class="comment">//设置一个对应的资源解析器（一个标准的SAX实体）</span></div><div class="line">		beanDefinitionReader.setEntityResolver(<span class="keyword">new</span> ResourceEntityResolver(<span class="keyword">this</span>));</div><div class="line"></div><div class="line">		<span class="comment">//如果我们提供了一些自定义的初始化策略，这里可以执行</span></div><div class="line">		initBeanDefinitionReader(beanDefinitionReader);</div><div class="line"></div><div class="line">		<span class="comment">//加载bean的入口</span></div><div class="line">		loadBeanDefinitions(beanDefinitionReader);</div><div class="line">	&#125;</div><div class="line"></div><div class="line">	<span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">loadBeanDefinitions</span><span class="params">(XmlBeanDefinitionReader reader)</span> <span class="keyword">throws</span> BeansException, IOException </span>&#123;</div><div class="line">		<span class="comment">//以Resource方式获得配置文件的资源位置</span></div><div class="line">		Resource[] configResources = getConfigResources();</div><div class="line">		<span class="keyword">if</span> (configResources != <span class="keyword">null</span>) &#123;</div><div class="line">			reader.loadBeanDefinitions(configResources);</div><div class="line">		&#125;</div><div class="line">		<span class="comment">//以String的方式获得配置文件的位置</span></div><div class="line">		String[] configLocations = getConfigLocations();</div><div class="line">		<span class="keyword">if</span> (configLocations != <span class="keyword">null</span>) &#123;</div><div class="line">			reader.loadBeanDefinitions(configLocations);</div><div class="line">		&#125;</div><div class="line">	&#125;</div></pre></td></tr></table></figure>
<p>具体以何种方式读取到资源，依据调用构造函数时，使用的是哪一个，那顺便看下构造函数是咋样的。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//主要是这两个</span></div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">ClassPathXmlApplicationContext</span><span class="params">(String[] paths, Class&lt;?&gt; clazz, ApplicationContext parent)</span></span></div><div class="line">			<span class="keyword">throws</span> BeansException &#123;</div><div class="line"></div><div class="line">		<span class="keyword">super</span>(parent);</div><div class="line">		Assert.notNull(paths, <span class="string">"Path array must not be null"</span>);</div><div class="line">		Assert.notNull(clazz, <span class="string">"Class argument must not be null"</span>);</div><div class="line">		<span class="keyword">this</span>.configResources = <span class="keyword">new</span> Resource[paths.length];</div><div class="line">		<span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; paths.length; i++) &#123;</div><div class="line">			<span class="keyword">this</span>.configResources[i] = <span class="keyword">new</span> ClassPathResource(paths[i], clazz);</div><div class="line">		&#125;</div><div class="line">		refresh();</div><div class="line">	&#125;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">ClassPathXmlApplicationContext</span><span class="params">(String[] configLocations, <span class="keyword">boolean</span> refresh, ApplicationContext parent)</span></span></div><div class="line">			<span class="keyword">throws</span> BeansException &#123;</div><div class="line"></div><div class="line">		<span class="keyword">super</span>(parent);</div><div class="line">		setConfigLocations(configLocations);</div><div class="line">		<span class="keyword">if</span> (refresh) &#123;</div><div class="line">			refresh();</div><div class="line">		&#125;</div><div class="line">	&#125;</div></pre></td></tr></table></figure></p>
<p>如果用的是第一个，那资源的方式是以Resource的形式获取到的<br>否则是以String的方式来获取资源位置</p>
<p>第一种方式中需要给出一个Class&lt;?&gt; clazz的参数，官方说明是指从clazz对应的类在classpath中的路径中去加载paths中给出的资源。相当于clazz给你指定提供了一个基础路径，而paths位于这个路径下</p>
<p>第二种方式一般直接给出资源在classpath中的路径或者给出一个绝对路径也可以被加载</p>
<p>继续说loadBeanDefinitions<br>这里有两个读取BeanDefinition的入口，一个是直接给出Resource，另一个是给出配置文件的String路径</p>
<p>####1.Resource方式读取资源<br>跟之前讲过的外部容器的初始化方式一致，有所不同的是这个加载、注册bean的过程会执行多次。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">loadBeanDefinitions</span><span class="params">(Resource... resources)</span> <span class="keyword">throws</span> BeanDefinitionStoreException </span>&#123;</div><div class="line">		Assert.notNull(resources, <span class="string">"Resource array must not be null"</span>);</div><div class="line">		<span class="keyword">int</span> counter = <span class="number">0</span>;</div><div class="line">		<span class="keyword">for</span> (Resource resource : resources) &#123;</div><div class="line">			counter += loadBeanDefinitions(resource);</div><div class="line">		&#125;</div><div class="line">		<span class="keyword">return</span> counter;</div><div class="line">	&#125;</div></pre></td></tr></table></figure></p>
<p>给出多少个资源就执行多少次，同时还记录了总共加载的bean的数量</p>
<p>那么重点讲讲另一种方式加载、注册bean的过程.<br>同样的，这种方式也会被执行多次，给出多少String形式的资源就执行多少次，同时还记录总共注册了bean的数量。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">loadBeanDefinitions</span><span class="params">(String... locations)</span> <span class="keyword">throws</span> BeanDefinitionStoreException </span>&#123;</div><div class="line">		Assert.notNull(locations, <span class="string">"Location array must not be null"</span>);</div><div class="line">		<span class="keyword">int</span> counter = <span class="number">0</span>;</div><div class="line">		<span class="keyword">for</span> (String location : locations) &#123;</div><div class="line">			counter += loadBeanDefinitions(location);</div><div class="line">		&#125;</div><div class="line">		<span class="keyword">return</span> counter;</div><div class="line">	&#125;</div></pre></td></tr></table></figure></p>
<p>与另一种给出Resource有所不同的是，使用Resource读取资源时，用的是XmlBeanDefinitionReader，而加载String形式的资源，则是爱AbstractBeanDefinitionReader中实现的<br>代码太长，一点点看.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">ResourceLoader resourceLoader = getResourceLoader();</div><div class="line">	<span class="keyword">if</span> (resourceLoader == <span class="keyword">null</span>) &#123;</div><div class="line">		<span class="keyword">throw</span> <span class="keyword">new</span> BeanDefinitionStoreException(</div><div class="line">				<span class="string">"Cannot import bean definitions from location ["</span> + location + <span class="string">"]: no ResourceLoader available"</span>);</div><div class="line">	&#125;</div><div class="line"><span class="function"><span class="keyword">public</span> ResourceLoader <span class="title">getResourceLoader</span><span class="params">()</span> </span>&#123;</div><div class="line">	<span class="keyword">return</span> <span class="keyword">this</span>.resourceLoader;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>这里很好理解，我们需要获取一个ResourceLoader来将String形式的资源路径加载成Resource<br>    那这个ResourceLoader从哪里来？<br>    在上面refresh过程中，刷新BeanFactory的过程，refreshBeanFactory函数的loadBeanDefinitions子函数内，我们设置将当前context设置成了ResourceLoader.<br>    也就是说，用当前的Context来加载资源，默认使用的是DefaultResourceLoader<br>    继续往下走.<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">if</span> (resourceLoader <span class="keyword">instanceof</span> ResourcePatternResolver) &#123;</div><div class="line">		<span class="keyword">try</span> &#123;</div><div class="line">		<span class="comment">//将一个或多个指定位置的bean资源，转换成Resource形式的资源</span></div><div class="line">			Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);</div><div class="line"></div><div class="line">			<span class="comment">//这个明显就是加载bean的过程了</span></div><div class="line">			<span class="keyword">int</span> loadCount = loadBeanDefinitions(resources);</div><div class="line">			<span class="keyword">if</span> (actualResources != <span class="keyword">null</span>) &#123;</div><div class="line">				<span class="keyword">for</span> (Resource resource : resources) &#123;</div><div class="line">					actualResources.add(resource);</div><div class="line">				&#125;</div><div class="line">			&#125;</div><div class="line">			...</div><div class="line">			<span class="comment">//返回了加载的bean的数量</span></div><div class="line">			<span class="keyword">return</span> loadCount;</div><div class="line">		&#125;</div><div class="line">		<span class="keyword">catch</span> (IOException ex) &#123;</div><div class="line">			...</div><div class="line">		&#125;</div><div class="line">	&#125;</div></pre></td></tr></table></figure></p>
<p>来看下他是如何将String类型的资源形式加载成Resource的<br>    跟踪可以看到，最终是在PathMatchingResourcePatternResolver里实现的<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> Resource[] getResources(String locationPattern) <span class="keyword">throws</span> IOException &#123;</div><div class="line">		Assert.notNull(locationPattern, <span class="string">"Location pattern must not be null"</span>);</div><div class="line">		<span class="keyword">if</span> (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) &#123;<span class="comment">//以classpath*:开头，以类路径的形式给出</span></div><div class="line">			<span class="comment">// 判断路径中是否有通配符（*，？）</span></div><div class="line">			<span class="keyword">if</span> (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) &#123;</div><div class="line">				<span class="comment">// 加载符合这些规则的资源  比如 classpath*: **/*DAO.class 这样的路径会走到该分支</span></div><div class="line">				<span class="keyword">return</span> findPathMatchingResources(locationPattern);</div><div class="line">			&#125;</div><div class="line">			<span class="keyword">else</span> &#123;</div><div class="line">				<span class="comment">// 如果路径中没有通配符，那就说明已经给出了具体路径，直接加载</span></div><div class="line">				<span class="keyword">return</span> findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));</div><div class="line">			&#125;</div><div class="line">		&#125;</div><div class="line">		<span class="comment">//else部分解析的不是以classpath*:开头，说明不是以类路径形式给出，而是一个文件路径 file: 这样的形式或者不带有前缀</span></div><div class="line">		<span class="keyword">else</span> &#123;</div><div class="line">			<span class="comment">//直接看第一个:后的路径，拿掉第一个前缀</span></div><div class="line">			<span class="keyword">int</span> prefixEnd = locationPattern.indexOf(<span class="string">":"</span>) + <span class="number">1</span>;</div><div class="line">			<span class="keyword">if</span> (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) &#123;</div><div class="line">				<span class="comment">//同样的拿掉了 前缀: 后如果还有通配符（*，？），继续按规定加载</span></div><div class="line">				<span class="keyword">return</span> findPathMatchingResources(locationPattern);</div><div class="line">			&#125;</div><div class="line">			<span class="keyword">else</span> &#123;</div><div class="line">				<span class="comment">//拿掉了前缀: 后，如果不含通配符；或者没有前缀，说明直接就是个单资源路径</span></div><div class="line">				<span class="keyword">return</span> <span class="keyword">new</span> Resource[] &#123;getResourceLoader().getResource(locationPattern)&#125;;</div><div class="line">			&#125;</div><div class="line">		&#125;</div><div class="line">	&#125;</div></pre></td></tr></table></figure></p>
<p>继续看他是如何加载成Resource的。<br>本想将整个加载过程拿出来讲的，而且也是与BeanFactory分支比较大的区别之一。但是我看了下代码感觉实在是有点多，拿出来一个个讲太占篇幅。所以就不给出源代码了，直接讲他的过程跟原理。</p>
<pre><code>以String类型给出的资源路径包括三种形式的
1.类路径  例子 classpath*：**/*.xml   classpath*:com/cc/a.xml
2.文件路径 例子 file:C:/myhome/a.xml  file:C:/myhome/*.xml  
3.不带前缀的 例子 com/myhome/**/*.xml 
</code></pre><p>首先是类路径，分为两种，一种是带通配符的，一种是不带通配符的.<br>    <strong>不带通配符的</strong><br>    不带通配符相当于直接给出了资源文件，不用进行匹配了。所以直接去他所在目录下去找他就好了<br>    例：classpath<em>:com/cc/a.xml   去classes/com/cc 下去找a.xml文件<br>        classpath</em>:a.xml  去classes/ 找a.xml文件</p>
<p><strong>带通配符的</strong><br>    这个相对麻烦点，不仅是匹配问题。<br>    例：classpath<em>：</em>.xml<br>    因为有通配符，我们先获取资源文件所在目录，这里是classes，所以，他先加载了classes/。<br>    进行匹配是时，将classes下的所有子目录以及classes目录下的直接文件加载进来，然后逐一匹配。<br>    匹配样例： /Users/admin51/git/lwy/target/classes/<em>.xml   匹配    /Users/admin51/git/lwy/target/classes/login.ftl<br>    可能我们觉得这样就完了，然而并不，因为文件所在目录是classe目录，所以还有一步特殊操作。<br>    它还会把你项目中所引用到的jar包都加载进来，然后把每个jar包下的文件都解析出来，比如com/oracle/tools/packager/Bundler.class。然后与解析出来的文件进行匹配.<br>    匹配样例：</em>.xml  匹配  com/oracle/tools/packager/Bundler.class<br>    恩，可能会感到奇怪为何直接用<em>.xml进行匹配，因为我们在匹配前需要根据待匹配资源属性来决定是全路径匹配还是文件名匹配。<br>    比如如果是jar资源，则用文件名进行匹配。如果是文件资源类型，则使用全路径进行匹配<br>类似的，如果资源是classpath</em>：templates/*.xml这样的形式，不是直接的classes目录<br>    则，先把classes/templates/加载进来，进行匹配的时候再把classes/templates/ 下全部的文件都加载进来，然后逐一匹配。并不用匹配jar文件，这是比较特殊的一点。</p>
<pre><code>类路径的加载方式说的差不多，文件路径与类路径基本一致
</code></pre><p>还有一种就是不带前缀的<br>    例： <em>.xml<br>        com/qwf/</em>.xml</p>
<p>基本就是这两种情况，一种没有目录，一种有目录。区别在哪里呢。<br>        首先要认识到一点，不带前缀默认就是根目录（classes）<br>        <em>.xml这种方式进行加载时，直接加载classes/ 目录，然后加载classes/ 下的所有文件进行匹配，当然，jar文件也会被加载进而匹配。<br>        像com/qwf/</em>.xml这种方式进行加载，默认环境是根目录(classes)，加上从这个路径中识别出来的com/qwf/，所以加载目录就成了classes/com/qwf/ ，进行加载时，先加载classes/com/qwf/ 目录，在加载classes/com/qwf/ 目录下所有文件进行匹配。返回匹配成功的资源。</p>
<p>那加载的方式说的差不多了，来说下它是如何进行匹配的。<br>        首先我们使用的目录分隔符是“/”，匹配是，将带匹配资源与目标资源全路径以String形式给出<br>        例：<br>        目标资源：/Users/admin51/git/lwy/target/classes/*.xml<br>        待匹配的：/Users/admin51/git/lwy/target/classes/beans.xml</p>
<p>用split函数将他们分隔开来，形成两个String数组，逐层对比，如果不同，则返回false。<br>        不过我们看到他们前面部分都一样，唯有最后，一个是*.xml，另一个是beans.xml，这里如何匹配。</p>
<pre><code>跟踪程序可以发现，最终匹配是用Java的正则来完成匹配的，beans.xml  与  .*\Q.xml\E 进行匹配
    \Q 的意思是引用后面所有的元字符直到遇到；\E 是配合 \Q 而存在的
    由此， .*\Q.xml\E 的意思是 .*.xml  以这样的方式进行正则匹配。扩展一下，如果 *.xml 换成 be*.xml，是如何进行匹配的呢
    匹配表达式就变成了\Qbe\E.*\Q.xml\E   简化下就是be.*.xml
    这是一种匹配情况，另一种是带有**(任意目录)的，这个的话应该比较好理解，举个例子
    例：/Users/admin51/git/**/cc/*.xml
        /Users/admin51/git/lwy/target/classes/beans.xml
        因为是比较到git之后出现**，所以先看下**后面还有没有目录，有的话，像例子中，取出cc，然后观察待匹配资源从git之后，有没有包含cc目录的，若没有，返回false。
        如果有或者有多个cc，则从最后一个cc开始继续完成匹配。
        若是/Users/admin51/git/**/*.xml 这样的形式，就比较麻烦，需要加载从git开始的每一级目录内的所有文件，比较有没有匹配*.xml的资源。
        所以使用ApplicationContext显示加载资源时，不要用类似 **/*.xml 这样的形式，因为会比较非常多的文件。而且很多都是无意义的匹配，拖慢加载速度。
</code></pre><p>那说到这里，使用ApplicationContext的资源加载获取基本就完成了，而后的步骤就是讲Resource数组内的加载到Document，再从Document加载到BeanDefinition，最后注册到DefaultListableBeanFactory，过程与之前说到内部容器的初始化、注册过程一样了。唯有不同的就是，BeanFactory分支，只对单一Resource进行加载。而ApplicationContext分支则对一组Resource进行加载，这也是体现ApplicationContext作为高级容器的一个特征。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>回顾一下ApplicationContext对于资源的加载与注册于BeanFactory分支的区别。</p>
<blockquote>
<p>1.BeanFactory一次只能加载一个Resource，而ApplicationContext一次可以加载多个Resource<br>2.BeanFactory与ApplicationContext获取资源的方式不同，BeanFactory直接去找对应资源而ApplicationContext有可能需要进行资源匹配<br>  这里多说一点，比如BeanFactory去加载Resource的时候，一个Resource就是一个Resource。而ApplicationContext可能是从几百个待挑选的Resource中选出了批匹配合格的Resource进行加载。所以，建议如果是显示的调用ApplicationContext进行资源加载的话，不要使用类似”classpath<em>:**/</em>.xml”这样的形式，因为会白白增加很多无谓的比较。<br>3.ApplicationContext提供了比BeanFactory更多的功能，这是肯定的，作为一个高级IoC容器，提供的功能肯定要比BeanFactory多一些</p>
</blockquote>

      
    </div>

    <div>
      
        

      
    </div>

    <div>
      
        

      
    </div>

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/spring/" rel="tag">#spring</a>
          
            <a href="/tags/ioc/" rel="tag">#ioc</a>
          
        </div>
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2016/08/28/spring-ioc-1/" rel="next" title="源码角度解析SpringIoC的机制">
                <i class="fa fa-chevron-left"></i> 源码角度解析SpringIoC的机制
              </a>
            
          </div>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2016/08/28/java-class-load/" rel="prev" title="">
                 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          
  <div class="comments" id="comments">
    
      <div class="ds-thread" data-thread-key="2016/08/28/spring-ioc-2/"
           data-title="从源码解析SpringIoc机制（2）" data-url="http://yoursite.com/2016/08/28/spring-ioc-2/">
      </div>
    
  </div>


        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap" >
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview sidebar-panel ">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
          <img class="site-author-image" itemprop="image"
               src="/uploads/touxiang.jpg"
               alt="钱伟峰" />
          <p class="site-author-name" itemprop="name">钱伟峰</p>
          <p class="site-description motion-element" itemprop="description">每天进步一点点</p>
        </div>
        <nav class="site-state motion-element">
          <div class="site-state-item site-state-posts">
            <a href="/archives">
              <span class="site-state-item-count">21</span>
              <span class="site-state-item-name">日志</span>
            </a>
          </div>

          
            <div class="site-state-item site-state-categories">
              <a href="/categories">
                <span class="site-state-item-count">7</span>
                <span class="site-state-item-name">分类</span>
              </a>
            </div>
          

          
            <div class="site-state-item site-state-tags">
              <a href="/tags">
                <span class="site-state-item-count">11</span>
                <span class="site-state-item-name">标签</span>
              </a>
            </div>
          

        </nav>

        

        <div class="links-of-author motion-element">
          
        </div>

        
        

        
        

      </section>

      
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">
            
              
            
            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-外部容器概述"><span class="nav-number">1.</span> <span class="nav-text">1.外部容器概述</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-原理分析"><span class="nav-number">2.</span> <span class="nav-text">2.原理分析</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#小结"><span class="nav-number">3.</span> <span class="nav-text">小结</span></a></li></ol></div>
            
          </div>
        </section>
      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright" >
  
  &copy; 
  <span itemprop="copyrightYear">2017</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">钱伟峰</span>
</div>

<div class="powered-by">
  由 <a class="theme-link" href="https://hexo.io">Hexo</a> 强力驱动
</div>

<div class="theme-info">
  主题 -
  <a class="theme-link" href="https://github.com/iissnan/hexo-theme-next">
    NexT.Muse
  </a>
</div>

        

        
      </div>
    </footer>

    <div class="back-to-top">
      <i class="fa fa-arrow-up"></i>
    </div>
  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  



  
  <script type="text/javascript" src="/vendors/jquery/index.js?v=2.1.3"></script>

  
  <script type="text/javascript" src="/vendors/fastclick/lib/fastclick.min.js?v=1.0.6"></script>

  
  <script type="text/javascript" src="/vendors/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>

  
  <script type="text/javascript" src="/vendors/velocity/velocity.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/vendors/velocity/velocity.ui.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/vendors/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.0.1"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.0.1"></script>



  
  

  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.0.1"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.0.1"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.0.1"></script>



  

  
    
  

  <script type="text/javascript">
    var duoshuoQuery = {short_name:"qianweifeng"};
    (function() {
      var ds = document.createElement('script');
      ds.type = 'text/javascript';ds.async = true;
      ds.id = 'duoshuo-script';
      ds.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//static.duoshuo.com/embed.js';
      ds.charset = 'UTF-8';
      (document.getElementsByTagName('head')[0]
      || document.getElementsByTagName('body')[0]).appendChild(ds);
    })();
  </script>

  
    
    <script src="/vendors/ua-parser-js/dist/ua-parser.min.js?v=0.7.9"></script>
    <script src="/js/src/hook-duoshuo.js"></script>
  






  
  

  

  

  

</body>
</html>
